package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-02-27 10:26
 */
@Service
public class TestServiceServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //Override
    //public void testLock() {
    //    //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
    //    String value = stringRedisTemplate.opsForValue().get("num");
    //    if (StringUtils.isBlank(value)) {
    //        return;
    //    }
    //    //2.对获取到值进行+1操作
    //    int num = Integer.parseInt(value);
    //    stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    //}


    /**
     * 案例:在单节点以及集群节点环境下,从Redis中获取数据,对该数据进行自增+1效果
     * 单节点下:synchronized,Lock 保证线程安全
     * 集群节点下:采用分布式锁 保证线程安全
     */
    //@Override
    //public void testLock() {
    //    //0.先尝试获取锁-向redis共享服务中通过"setnx"存数据(锁)
    //    //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock");
    //    //todo setnx后续最新redis中该命令废弃
    //    // stringRedisTemplate.expire("lock", Duration.ofSeconds(2));
    //    //使用set ex nx 等参数保证 存 设置过期时间原子性
    //    //生成锁的值 保证唯一
    //    String uuid = UUID.randomUUID().toString().replaceAll("-", "");
    //    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
    //    if (flag) {
    //        try {
    //            //0.1 获取锁成功(存入数据成功),执行业务逻辑
    //            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
    //            String value = stringRedisTemplate.opsForValue().get("num");
    //            if (StringUtils.isBlank(value)) {
    //                return;
    //            }
    //            //2.对获取到值进行+1操作
    //            int num = Integer.parseInt(value);
    //            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    //        } catch (NumberFormatException e) {
    //            e.printStackTrace();
    //        } finally {
    //            //3.将锁手动释放 问题1:锁被不同线程可能误删
    //            //stringRedisTemplate.delete("lock");
    //            //3.1 先从redis获取锁的值 判断是否跟当前线程锁的值相同 如果相同才能删除
    //            //String lockVal = stringRedisTemplate.opsForValue().get("lock");
    //            ////线程1判断该锁是自己的
    //            //if (uuid.equals(lockVal)) {
    //            //    //锁正好失效,其他线程2获取锁成功
    //            //    stringRedisTemplate.delete("lock");  //线程1 删除线程2锁
    //            //}else{
    //            //    System.err.println("尝试释放其他线程的锁!!!!");
    //            //}
    //            //问题2:判断跟删除无法保证原子性,导致锁互相释放
    //            //解决:采用Lua脚本语言,Redis中封装Lua脚本,保证多个redis命令执行原子性
    //            //参数1:封装脚本对象  参数2:传入key的参数值  参数3:传入argv参数值
    //            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
    //                    "then\n" +
    //                    "    return redis.call(\"del\",KEYS[1])\n" +
    //                    "else\n" +
    //                    "    return 0\n" +
    //                    "end";
    //            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    //            redisScript.setScriptText(script);
    //            stringRedisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
    //        }
    //    } else {
    //        //0.2 获取锁成功,重试(自旋)
    //        try {
    //            Thread.sleep(100);
    //            this.testLock();
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
    //    }
    //}


    @Autowired
    private RedissonClient redissonClient;

    /**
     * 采用Redisson实现分布式锁
     */
    @Override
    public void testLock() {
        //0.1 创建锁对象
        RLock lock = redissonClient.getLock("lock");

        //0.2 获取锁
        //0.2.1 一直等待到获取锁,如果获取锁成功,锁的有效时间为:30s,底层启动"看门狗"线程(如果业务有超时风险)可以延迟锁有效时间
        lock.lock();

        //0.2.2 一直等待到获取锁,如果获取锁成功,自定义锁有效时间
        //lock.lock(10, TimeUnit.SECONDS);

        //0.2.3 尝试获取锁 参数1:等待获取锁时间,超时则放弃获取  参数2:如果获取锁成功,锁的有效时间 参数3:时间单位
        //boolean b = lock.tryLock(3, 10, TimeUnit.SECONDS);
        try {
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = stringRedisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

            //2.1 测试可重入锁
            this.check();
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            //3. 释放锁
            lock.unlock();
        }
    }



    /**
     * 测试Redisson可重入性
     */
    private void check() {
        //获取锁
        RLock lock = redissonClient.getLock("lock");
        lock.lock();
        //执行业务
        System.out.println("执行重入方法");
        //释放锁
        lock.unlock();
    }


    /**
     * "读"数据方法
     *
     * @return
     */
    @Override
    public String read() {
        //1.创建锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("rwlock");
        //2.获取读锁
        RLock lock = rwlock.readLock();
        lock.lock(5, TimeUnit.SECONDS);
        //3.执行业务
        System.out.println("执行读数据业务");
        //4.将锁释放 todo 不手动释放,等待锁超时自动释放
        return null;
    }

    /**
     * "写"数据方法
     */
    @Override
    public void write() {
        //1.创建锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("rwlock");
        //2.获取写锁
        RLock lock = rwlock.writeLock();
        lock.lock(5, TimeUnit.SECONDS);
        //3.执行业务
        System.out.println("执行写数据业务");

        //4.将锁释放 todo 不手动释放,等待锁超时自动释放
    }



}
